package com.grass.plugin.paging.ibatis;

import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.ResultMapping;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import com.grass.plugin.DialectFactory;
import com.grass.plugin.dialect.Dialect;
import com.grass.plugin.paging.page.PageContext;
 

/**
 * ibatis 拦截器用于实现分页
 * QQ 396246973
 * Date 2013-02-10
 * @author WUJXIAO
 */
@Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class,ResultHandler.class}),
    @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
public class PagingPlugin implements Interceptor {
 
	private String dialect;
    private static final String BOUND_SQL = "sqlSource.boundSql.sql";
    private static final List<ResultMapping> EMPTY_RESULTMAPPING = new ArrayList<ResultMapping>(0);
   
    public static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
    public static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();
   
    /**
    * 反射对象，增加对低版本Mybatis的支持
    *
    * @param object
    * @return
    */
    public static MetaObject forObject(Object object) {
       return MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY);
    }
   
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
       final Object[] args = invocation.getArgs();
       final MappedStatement ms = (MappedStatement) args[0];
       final Object parameter = args[1];
       final BoundSql boundSql = ms.getBoundSql(parameter);
       final Dialect dbDialect = DialectFactory.getDialectByDbtype(dialect);
      
       String formatSql =  boundSql.getSql().trim().replaceAll("[\r\n]", " ").replaceAll("\\s{2,}", " ");
       formatSql = dbDialect.Escape(formatSql);
       
       final MappedStatement nms = newMappedStatement(ms, new BoundSqlSqlSource(boundSql));
       args[0] = nms;      
       MetaObject mo = forObject(nms);
       if("query".equals(invocation.getMethod().getName())){
    	   PageContext page = PageContext.getContext();
    	   if (page.isPagination()) {
    		  //忽略RowBounds-否则会进行Mybatis自带的内存分页
               args[2] = RowBounds.DEFAULT;
               mo.setValue(BOUND_SQL, "select count(0) from (" + formatSql + ") tmp_count");//获取记录总数
               List<ResultMap> resultMaps = new ArrayList<ResultMap>();
               resultMaps.add(new ResultMap.Builder(ms.getConfiguration(), ms.getId(), int.class, EMPTY_RESULTMAPPING).build());              
               mo.setValue("resultMaps", resultMaps);
               Object result = ((List<?>)invocation.proceed()).get(0);
               page.init((Integer) result, page.getPageSize(), page.getCurrentPage());//分页计算
              
               String newSql = dbDialect.getLimitString(formatSql, page);
	   		   if(!dbDialect.NativePaging()){//方言分页失败数据库处理方式，改用游标处理分页
	   				args[2] = new RowBounds(page.getPageStartRow(), page.getPageSize());
	   		   }
	   		   mo.setValue(BOUND_SQL, newSql);//获取记录总数
               mo.setValue("resultMaps", ms.getResultMaps());
               return invocation.proceed();
    	   }
       }
       mo.setValue(BOUND_SQL, formatSql);
       return invocation.proceed();
    }
   
    private MappedStatement newMappedStatement(MappedStatement ms, SqlSource newSqlSource) {
       MappedStatement.Builder builder = new MappedStatement.Builder(
              ms.getConfiguration(), ms.getId() + "_PageHelper",
              newSqlSource, ms.getSqlCommandType());
      
       builder.cache(ms.getCache());
       builder.databaseId(ms.getDatabaseId());
       builder.fetchSize(ms.getFetchSize());
       String tmp = "";
       if (null != ms.getKeyColumns()) {
           for (String c : ms.getKeyColumns())
              tmp += "," + c;
           builder.keyColumn(tmp.replaceFirst(",", ""));
           tmp = "";
       }
       builder.keyGenerator(ms.getKeyGenerator());
       if (null != ms.getKeyProperties()) {
           for (String k : ms.getKeyColumns())
              tmp += "," + k;
           builder.keyProperty(tmp.replaceFirst(",", ""));
           tmp = "";
       }
       builder.lang(ms.getLang());
       builder.parameterMap(ms.getParameterMap());
       builder.resource(ms.getResource());
       builder.resultMaps(ms.getResultMaps());
       builder.resultSetType(ms.getResultSetType());
       builder.statementType(ms.getStatementType());
       builder.timeout(ms.getTimeout());
       builder.flushCacheRequired(ms.isFlushCacheRequired());
       builder.resultOrdered(ms.isResultOrdered());
       builder.useCache(ms.isUseCache());
       //ms.getStatementLog();//mo.setValue("statementLog", ms.getStatementLog());
       return builder.build();
    }
   
    private class BoundSqlSqlSource implements SqlSource {
        private BoundSql boundSql;
        public BoundSqlSqlSource(BoundSql boundSql) {
            this.boundSql = boundSql;
        }
        public BoundSql getBoundSql(Object parameterObject) {
            return boundSql;
        }
    }
 
    @Override
    public Object plugin(Object target) {
       if (target instanceof Executor) {
           return Plugin.wrap(target, this);
       } else {
           return target;
       }
    }
 
    @Override
    public void setProperties(Properties properties) {
    }
    
    public void setDialect(String dialect) {
        this.dialect = dialect;
    }
}